/**
 * @file bmp_show.c
 * @author your name (you@domain.com)
 * @brief Framebuffer 应用程序示例代码：显示 BMP图像
 * @version 0.1
 * @date 2023-06-25
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/fb.h>

/**
 * @brief BMP 文件头数据结构
 * 
 */
typedef struct {
    unsigned char type[2]; /* 文件类型 */
    unsigned int size; /* 文件大小 */
    unsigned short reserved1; /* 保留字段1 */
    unsigned short reserved2; /* 保留字段2 */
    unsigned int offset; /* 到位图数据的偏移量 */
} __attribute__((packed)) bmp_file_header;

/**
 * @brief 位图信息头数据结构
 * 
 */
typedef struct {
    unsigned int size; /* 位图信息头大小 */
    int width; /* 图像宽度 */
    int height; /* 图像高度 */
    unsigned short planes; /* 位面数 */
    unsigned short bpp; /* 像素深度 */
    unsigned int compression; /* 压缩方式 */
    unsigned int image_size; /* 图像大小 */
    int x_pels_per_meter; /* 像素/米 */
    int y_pels_per_meter; /* 像素/米 */
    unsigned int clr_used;
    unsigned int clr_omportant;
} __attribute__((packed)) bmp_info_header;

/**
 * @brief 静态全局变量
 * 
 */
static int width; /* LCD X 分辨率 */
static int height; /* LCD Y 分辨率 */
static unsigned short *screen_base = NULL; /* 映射后的显存基地址 */
static unsigned long line_length; /* LCD一行的长度（字节为单位） */

/**
 * @brief 在 LCD上显示指定的 BMP图片
 * 
 * @param path 文件路径
 * @return int 成功返回 0,失败返回 1
 */
static int show_bmp_image(const char *path)
{
    bmp_file_header file_h;
    bmp_info_header info_h;
    unsigned short *line_buf = NULL; /* 行缓冲区 */
    unsigned long line_bytes; /* BMP 图像一行字节数 */
    unsigned int min_h, min_bytes;
    int fd = -1;
    /* 打开文件 */
    fd = open(path, O_RDONLY);
    if (0 > fd) {
        perror("open error");
        return -1;
    }
    /* 读取 BMP文件头 */
    if (sizeof(bmp_file_header) != read(fd, &file_h, sizeof(bmp_file_header))) {
        perror("read bmp_file_header error");
        close(fd);
        return -1;
    }
    if (0 != memcmp(file_h.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(fd);
        return -1;
    }
    /* 读取位图信息头 */
    if (sizeof(bmp_info_header) != read(fd, &info_h, sizeof(bmp_info_header))) {
        perror("read bmp_info_header error");
        close(fd);
        return -1;
    }
    /* 打印信息 */
    printf("文件大小: %d\n", file_h.size);
    printf("位图数据的偏移量: %d\n", file_h.offset);
    printf("位图信息头大小: %d\n", info_h.size);
    printf("图像分辨率: %d * %d\n", info_h.width, info_h.height);
    printf("像素深度: %d\n", info_h.bpp);
    /* 将文件读写位置移动到图像数据开始处 */
    if (0 > lseek(fd, file_h.offset, SEEK_SET)) {
        perror("lseek error");
        close(fd);
        return -1;
    }
    /* 申请一个 buf，暂存 bmp图像的一行数据 */
    line_bytes = info_h.width * info_h.bpp / 8;
    line_buf = malloc(line_bytes);
    if (NULL == line_buf) {
        fprintf(stderr, "malloc error\n");
        close(fd);
        return -1;
    }
    if (line_length > line_bytes) {
        min_bytes = line_bytes;
    } else {
        min_bytes = line_length;
    }
    /* 读取图像数显示到 LCD。默认传入的 bmp图像是 RGB565格式，不做兼容处理 */
    if (0 < info_h.height) { /* 倒向位图 */
        if (info_h.height > height) {
            min_h = height;
            lseek(fd, (info_h.height - height) * line_bytes, SEEK_CUR);
            screen_base += width * (height - 1); /* 定位到屏幕左下角位置 */
        } else {
            min_h = info_h.height;
            screen_base += width * (info_h.height - 1); /* 定位到 */
        }
        for (int j = min_h; j > 0; screen_base -= width, j--) {
            read(fd, line_buf, line_bytes); /* 读取出图像数据*/
            memcpy(screen_base, line_buf, min_bytes); /* 刷入 LCD缓存 */
        }
    } else { /* 正向位图 */
        int temp = 0 - info_h.height; /* 负数转成正数 */
        if (temp > height) { 
            min_h = height; 
        } else { 
            min_h = temp; 
        }
        for (int j = 0; j < min_h; j++, screen_base += width) { 
            read(fd, line_buf, line_bytes);
            memcpy(screen_base, line_buf, min_bytes);
        }
    }
    /* 关闭文件、函数返回 */
    close(fd);
    free(line_buf);
    return 0;
}

int main(int argc, char const *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 校验传参 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <bmp_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 打开 framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    screen_size = fb_fix.line_length * fb_var.yres;
    line_length = fb_fix.line_length;
    width = fb_var.xres;
    height = fb_var.yres;
    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void*)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }
    /* 显示 BMP图片 */
    memset(screen_base, 0xFF, screen_size);
    show_bmp_image(argv[1]);
    /* 退出 */
    munmap(screen_base, screen_size); /* 取消映射 */
    close(fd); /* 关闭文件 */
    exit(EXIT_SUCCESS); /* 退出进程 */
}
